/**
 * 指南针控件
 * @example
 * <Compass
 *      viewer={viewer}
 *      homeView={homeView}
 *      sceneMode={sceneMode}
 *      compassWidth={66}
 *      ringWidth={18}
 * />
 */
const Compass = React.createClass({
    displayName: 'Compass',
    getInitialState() {
        /**
         * 整个指南针的大小
         * @type {number}
         */
        this.compassWidth = this.props.compassWidth;
        /**
         * 外环的宽度
         * @type {number}
         */
        this.ringWidth = this.props.ringWidth;
        /**
         * 组件的状态
         * @type {object}
         * @property {number} orbitCursorAngle 陀螺仪指针旋转角度
         * @property {number} heading 相机朝向
         * @property {number} pitch 相机俯仰角
         * @property {number} orbitCursorOpacity 陀螺仪指针透明度
         * @property {boolean} enableGyro 开启陀螺仪功能
         * @property {boolean} enableReset 开启复位功能
         * @property {boolean} enableRotate 开启旋转功能
         */
        return {
            orbitCursorAngle: 0,
            heading: 0.0,
            pitch: 0.0,
            orbitCursorOpacity: 0,
            enableGyro: true,
            enableReset: true,
            enableRotate: true
        };
    },
    componentWillReceiveProps(nextProps) {
        if (nextProps.sceneMode === 2) {
            this.setState({
                enableGyro: false,
                enableReset: false,
                enableRotate: false
            })
        }
        else if (nextProps.sceneMode === 3) {
            this.setState({
                enableGyro: true,
                enableReset: true,
                enableRotate: true
            })
        }
    },
    componentDidMount() {
        //当相机发生变化时，调用viewChange函数
        this._unsubscribeFromViewerChange = this.props.viewer.scene.camera.changed.addEventListener(() => viewerChange(this));
        viewerChange(this);
    },
    componentWillUnmount() {
        document.removeEventListener('mousemove', this.orbitMouseMoveFunction, false);
        document.removeEventListener('mouseup', this.orbitMouseUpFunction, false);
        // this._unsubscribeFromClockTick && this._unsubscribeFromClockTick();
        this._unsubscribeFromPostRender && this._unsubscribeFromPostRender();
        this._unsubscribeFromViewerChange && this._unsubscribeFromViewerChange();
    },
    /**
     * 鼠标左键按下事件，开始旋转
     * @param {object} e 事件对象
     * @returns {boolean}
     */
    handleMouseDown(e) {
        if (e.clientX) {
            if (e.stopPropagation) e.stopPropagation();
            if (e.preventDefault) e.preventDefault();
        }

        const compassElement = e.currentTarget;
        const compassRectangle = e.currentTarget.getBoundingClientRect();
        const maxDistance = compassRectangle.width / 2.0;
        const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
        const clickLocation = new Cesium.Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top);
        // console.log(clickLocation);
        const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
        const distanceFromCenter = Cesium.Cartesian2.magnitude(vector);

        const distanceFraction = distanceFromCenter / maxDistance;

        //控制指南针陀螺仪的灵敏度，不是一按下去就变化的
        const nominalTotalRadius = 145;
        const norminalGyroRadius = 50;

        if (distanceFraction < norminalGyroRadius / nominalTotalRadius && this.state.enableGyro) {
            orbit(this, compassElement, vector);
        } else if (distanceFraction < 1.0) {
            if (this.state.enableRotate) {
                rotate(this, compassElement, vector);
            }
        } else {
            return true;
        }
    },
    /**
     * 鼠标双击事件，视角复位
     * @param {object} e 事件对象
     */
    handleDoubleClick(e) {
        const scene = this.props.viewer.scene;
        const camera = scene.camera;

        const windowPosition = windowPositionScratch;
        windowPosition.x = scene.canvas.clientWidth / 2;
        windowPosition.y = scene.canvas.clientHeight / 2;

        let center = new Cesium.Cartesian3();
        if (this.state.enableReset) {
            const ray = camera.getPickRay(windowPosition, pickRayScratch);
            center = scene.globe.pick(ray, scene, centerScratch);
            if (!Cesium.defined(center)) {
                // Globe is barely visible, so reset to home view.
                this.props.viewer.zoomTo(this.props.homeView, 1.5);
                return;
            }

            //以屏幕中心所在地表上的点为原点的局部坐标系矩阵
            const rotateFrame = Cesium.Transforms.eastNorthUpToFixedFrame(center, Cesium.Ellipsoid.WGS84);
            //摄像机到局地坐标原点的向量，方向向量
            const lookVector = Cesium.Cartesian3.subtract(center, camera.position, new Cesium.Cartesian3());

            /**
             * destination: 局地坐标与原点上方一点（还原后的相机位置）的叉乘，相当于将相机位置从局地坐标转换为世界坐标
             * direction: 还原后的相机朝向，原理与destination相同
             * up: 还原后的相机上方向，原理与direction相同
             */
            const flight = Cesium.CameraFlightPath.createTween(scene, {
                destination: Cesium.Matrix4.multiplyByPoint(rotateFrame, new Cesium.Cartesian3(0.0, 0.0, Cesium.Cartesian3.magnitude(lookVector)), new Cesium.Cartesian3()),
                direction: Cesium.Matrix4.multiplyByPointAsVector(rotateFrame, new Cesium.Cartesian3(0.0, 0.0, -1.0), new Cesium.Cartesian3()),
                up: Cesium.Matrix4.multiplyByPointAsVector(rotateFrame, new Cesium.Cartesian3(0.0, 1.0, 0.0), new Cesium.Cartesian3()),
                duration: 1.5
            });
            scene.tweens.add(flight);
        }
        else {
            // center = camera.pickEllipsoid(windowPosition);
        }
    },
    /**
     * 手指按压事件，开启旋转
     * @param {object} e 事件对象
     * @returns {boolean}
     */
    handleTouchStart(e) {
        console.log('touch Start');
        const compassElement = e.currentTarget;
        const compassRectangle = e.currentTarget.getBoundingClientRect();
        const maxDistance = compassRectangle.width / 2.0;
        const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
        const clickLocation = new Cesium.Cartesian2(e.touches[0].clientX - compassRectangle.left, e.touches[0].clientY - compassRectangle.top);

        const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
        const distanceFromCenter = Cesium.Cartesian2.magnitude(vector);

        const distanceFraction = distanceFromCenter / maxDistance;

        //控制指南针陀螺仪的灵敏度，不是一按下去就变化的
        const nominalTotalRadius = 145;
        const norminalGyroRadius = 50;

        // console.log('nav start');
        // console.log(clickLocation);
        // console.log(center);
        // console.log(distanceFraction);
        if (distanceFraction < norminalGyroRadius / nominalTotalRadius && this.state.enableGyro) {
            orbit(this, compassElement, vector);
        } else if (distanceFraction < 1.0) {
            if (this.state.enableRotate) {
                rotate(this, compassElement, vector);
            }
        } else {
            return true;
        }
    },
    /**
     * 还原相机后，重置陀螺仪参数
     */
    resetRotater() {
        this.setState({
            orbitCursorOpacity: 0,
            orbitCursorAngle: 0
        })
    },
    render() {
        const description = '拖动外环: 旋转视角.\n拖动内部陀螺仪图标: 自由视角。\n双击: 重置视角。';
        const styles = {
            compass: {
                position: "relative",
                cursor: "default",
                width: this.compassWidth + 'px',
                height: this.compassWidth + 'px'
            },
            outerRing: {
                height: (this.compassWidth) + 'px',
                width: (this.compassWidth) + 'px',
                borderRadius: '50%',
                position: 'absolute',
                top: '0',
                left: '0',
                backgroundImage: 'url("images/compass-outer.svg")',
                backgroundRepeat: 'no-repeat',
                backgroundSize: 'contain',
                // boxShadow:'1px 1px 5px black',

                transform: 'rotate(-' + this.state.heading + 'rad)',
                WebkitTransform: 'rotate(-' + this.state.heading + 'rad)',
                opacity: ''
            },
            innerRing: {
                position: 'relative',
                // top: this.compassWidth/2+'px',
                // left: this.compassWidth/2+'px',
                top: '50%',
                transform: 'translateY(-50%) rotateX(' + (-this.state.pitch - Cesium.Math.PI / 2) + 'rad)',
                WebkitTransform: 'translateY(-50%) rotateX(' + (-this.state.pitch - Cesium.Math.PI / 2) + 'rad)',
                height: (this.compassWidth - this.ringWidth - 20) + 'px',
                width: (this.compassWidth - this.ringWidth - 20) + 'px',
                borderRadius: '50%',
                display: 'block',
                margin: '0 auto',
                backgroundImage: 'url("images/compass-inner.svg")',
                backgroundRepeat: 'no-repeat',
                backgroundSize: 'contain'
            },
            rotationMarker: {
                height: (this.compassWidth - 4) + 'px',
                width: (this.compassWidth - 4) + 'px',
                borderRadius: '50%',
                position: 'absolute',
                top: '2px',
                left: '2px',
                backgroundImage: 'url("images/compass-rotation-marker.svg")',
                backgroundRepeat: 'no-repeat',
                backgroundSize: 'contain',

                transform: 'rotate(-' + this.state.orbitCursorAngle + 'rad)',
                WebkitTransform: 'rotate(-' + this.state.orbitCursorAngle + 'rad)',
                opacity: this.state.orbitCursorOpacity
            }
        };
        return (
            <div style={styles.compass} title={description}
                 onMouseDown ={this.handleMouseDown}
                 onDoubleClick={this.handleDoubleClick}
                 onMouseUp={this.resetRotater}
                 onTouchStart={this.handleTouchStart}
                // onTouchMove={this.handleTouchMove}
                 onTouchEnd={this.resetRotater}
            >
                <div style={styles.outerRing}></div>
                <div style={styles.innerRing} title='点击拖动旋转相机视角'></div>
                <div style={styles.rotationMarker}></div>
            </div>
        );
    }
});

const vectorScratch = new Cesium.Cartesian2();
const oldTransformScratch = new Cesium.Matrix4();
const newTransformScratch = new Cesium.Matrix4();
const centerScratch = new Cesium.Cartesian3();
const windowPositionScratch = new Cesium.Cartesian2();
const pickRayScratch = new Cesium.Ray();

function rotate(viewModel, compassElement, cursorVector) {
    // Remove existing event handlers, if any.
    document.removeEventListener('mousemove', viewModel.rotateMouseMoveFunction, false);
    document.removeEventListener('mouseup', viewModel.rotateMouseUpFunction, false);
    document.removeEventListener('touchmove', viewModel.rotateMouseMoveFunction, false);
    document.removeEventListener('touchend', viewModel.rotateMouseUpFunction, false);

    viewModel.rotateMouseMoveFunction = undefined;
    viewModel.rotateMouseUpFunction = undefined;

    viewModel.isRotating = true;
    viewModel.rotateInitialCursorAngle = Math.atan2(-cursorVector.y, cursorVector.x);

    const scene = viewModel.props.viewer.scene;
    let camera = scene.camera;
    const windowPosition = windowPositionScratch;
    windowPosition.x = scene.canvas.clientWidth / 2;
    windowPosition.y = scene.canvas.clientHeight / 2;

    let viewCenter = new Cesium.Cartesian3();
    if(viewModel.state.enableReset){
        const ray = camera.getPickRay(windowPosition, pickRayScratch);
        viewCenter = scene.globe.pick(ray, scene, centerScratch);
    }
    else{
        viewCenter = camera.pickEllipsoid(windowPosition);
    }

    if (!Cesium.defined(viewCenter)) {
        viewModel.rotateFrame = Cesium.Transforms.eastNorthUpToFixedFrame(camera.positionWC, Cesium.Ellipsoid.WGS84, newTransformScratch);
        viewModel.rotateIsLook = true;
    } else {
        viewModel.rotateFrame = Cesium.Transforms.eastNorthUpToFixedFrame(viewCenter, Cesium.Ellipsoid.WGS84, newTransformScratch);
        viewModel.rotateIsLook = false;
    }

    let oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);
    camera.lookAtTransform(viewModel.rotateFrame);
    viewModel.rotateInitialCameraAngle = Math.atan2(camera.position.y, camera.position.x);
    viewModel.rotateInitialCameraDistance = Cesium.Cartesian3.magnitude(new Cesium.Cartesian3(camera.position.x, camera.position.y, 0.0));
    camera.lookAtTransform(oldTransform);

    viewModel.rotateMouseMoveFunction = function(e) {
        const compassRectangle = compassElement.getBoundingClientRect();
        const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
        const clickLocation = new Cesium.Cartesian2();
        if(e.clientX){
            clickLocation.x = e.clientX - compassRectangle.left;
            clickLocation.y = e.clientY - compassRectangle.top;
        }
        else if(e.touches[0].clientX){
            clickLocation.x = e.touches[0].clientX - compassRectangle.left;
            clickLocation.y = e.touches[0].clientY - compassRectangle.top;
        }
        const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
        const angle = Math.atan2(-vector.y, vector.x);

        const angleDifference = angle - viewModel.rotateInitialCursorAngle;
        const newCameraAngle = Cesium.Math.zeroToTwoPi(viewModel.rotateInitialCameraAngle - angleDifference);

        camera = viewModel.props.viewer.scene.camera;
        oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);
        camera.lookAtTransform(viewModel.rotateFrame);
        const currentCameraAngle = Math.atan2(camera.position.y, camera.position.x);
        if(viewModel.state.enableReset){
            camera.rotateRight(newCameraAngle - currentCameraAngle);
        }
        camera.lookAtTransform(oldTransform);

        // viewModel.props.viewer.notifyRepaintRequired();
    };

    viewModel.rotateMouseUpFunction = function(e) {
        viewModel.isRotating = false;
        document.removeEventListener('mousemove', viewModel.rotateMouseMoveFunction, false);
        document.removeEventListener('mouseup', viewModel.rotateMouseUpFunction, false);
        document.removeEventListener('touchmove', viewModel.rotateMouseMoveFunction, false);
        document.removeEventListener('touchend', viewModel.rotateMouseUpFunction, false);

        viewModel.rotateMouseMoveFunction = undefined;
        viewModel.rotateMouseUpFunction = undefined;
    };

    document.addEventListener('mousemove', viewModel.rotateMouseMoveFunction, false);
    document.addEventListener('mouseup', viewModel.rotateMouseUpFunction, false);
    document.addEventListener('touchmove', viewModel.rotateMouseMoveFunction, false);
    document.addEventListener('touchend', viewModel.rotateMouseUpFunction, false);
}

function orbit(viewModel, compassElement, cursorVector) {
    // Remove existing event handlers, if any.
    document.removeEventListener('mousemove', viewModel.orbitMouseMoveFunction, false);
    document.removeEventListener('mouseup', viewModel.orbitMouseUpFunction, false);
    document.removeEventListener('touchmove', viewModel.orbitMouseMoveFunction, false);
    document.removeEventListener('touchend', viewModel.orbitMouseUpFunction, false);

    if (Cesium.defined(viewModel.orbitTickFunction)) {
        viewModel.props.viewer.clock.onTick.removeEventListener(viewModel.orbitTickFunction);
    }

    viewModel.orbitMouseMoveFunction = undefined;
    viewModel.orbitMouseUpFunction = undefined;
    viewModel.orbitTickFunction = undefined;

    viewModel.isOrbiting = true;
    viewModel.orbitLastTimestamp = Cesium.getTimestamp();

    let scene = viewModel.props.viewer.scene;
    let camera = scene.camera;

    const windowPosition = windowPositionScratch;
    windowPosition.x = scene.canvas.clientWidth / 2;
    windowPosition.y = scene.canvas.clientHeight / 2;
    const ray = camera.getPickRay(windowPosition, pickRayScratch);

    let center = scene.globe.pick(ray, scene, centerScratch);
    if (!Cesium.defined(center)) {
        viewModel.orbitFrame = Cesium.Transforms.eastNorthUpToFixedFrame(camera.positionWC, Cesium.Ellipsoid.WGS84, newTransformScratch);
        viewModel.orbitIsLook = true;
    } else {
        viewModel.orbitFrame = Cesium.Transforms.eastNorthUpToFixedFrame(center, Cesium.Ellipsoid.WGS84, newTransformScratch);
        viewModel.orbitIsLook = false;
    }

    viewModel.orbitTickFunction = function(e) {
        const timestamp = Cesium.getTimestamp();
        const deltaT = timestamp - viewModel.orbitLastTimestamp;
        const rate = (viewModel.state.orbitCursorOpacity - 0.5) * 2.5 / 1000;
        const distance = deltaT * rate;

        const angle = viewModel.state.orbitCursorAngle + Cesium.Math.PI_OVER_TWO;
        let x = Math.cos(angle) * distance;
        let y = Math.sin(angle) * distance;

        scene = viewModel.props.viewer.scene;
        camera = scene.camera;

        const oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);

        camera.lookAtTransform(viewModel.orbitFrame);

        if (viewModel.orbitIsLook) {
            camera.look(Cesium.Cartesian3.UNIT_Z, -x);
            camera.look(camera.right, -y);
        } else {
            camera.rotateLeft(x);
            camera.rotateUp(y);
        }

        camera.lookAtTransform(oldTransform);

        // viewModel.props.terria.cesium.notifyRepaintRequired();

        viewModel.orbitLastTimestamp = timestamp;
    };

    function updateAngleAndOpacity(vector, compassWidth) {
        const angle = Math.atan2(-vector.y, vector.x);
        viewModel.setState({
            orbitCursorAngle: Cesium.Math.zeroToTwoPi(angle - Cesium.Math.PI_OVER_TWO)
        });

        const distance = Cesium.Cartesian2.magnitude(vector);
        const maxDistance = compassWidth / 2.0;
        const distanceFraction = Math.min(distance / maxDistance, 1.0);
        const easedOpacity = 0.5 * distanceFraction * distanceFraction + 0.5;
        viewModel.setState({
            orbitCursorOpacity: easedOpacity
        });

        // viewModel.props.terria.cesium.notifyRepaintRequired();
    }

    viewModel.orbitMouseMoveFunction = function(e) {
        const compassRectangle = compassElement.getBoundingClientRect();
        center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
        const clickLocation = new Cesium.Cartesian2();
        if(e.clientX){
            clickLocation.x = e.clientX - compassRectangle.left;
            clickLocation.y = e.clientY - compassRectangle.top;
        }
        else if(e.touches&&e.touches[0].clientX){
            clickLocation.x = e.touches[0].clientX - compassRectangle.left;
            clickLocation.y = e.touches[0].clientY - compassRectangle.top;
        }
        const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
        updateAngleAndOpacity(vector, compassRectangle.width);
    };

    viewModel.orbitMouseUpFunction = function(e) {
        viewModel.isOrbiting = false;
        document.removeEventListener('mousemove', viewModel.orbitMouseMoveFunction, false);
        document.removeEventListener('mouseup', viewModel.orbitMouseUpFunction, false);
        document.removeEventListener('touchmove', viewModel.orbitMouseMoveFunction, false);
        document.removeEventListener('touchend', viewModel.orbitMouseUpFunction, false);

        if (Cesium.defined(viewModel.orbitTickFunction)) {
            viewModel.props.viewer.clock.onTick.removeEventListener(viewModel.orbitTickFunction);
        }
        //设置marker的透明度为0
        viewModel.setState({
            orbitCursorOpacity: 0
        });

        viewModel.orbitMouseMoveFunction = undefined;
        viewModel.orbitMouseUpFunction = undefined;
        viewModel.orbitTickFunction = undefined;
    };

    document.addEventListener('mousemove', viewModel.orbitMouseMoveFunction, false);
    document.addEventListener('mouseup', viewModel.orbitMouseUpFunction, false);
    document.addEventListener('touchmove', viewModel.orbitMouseMoveFunction, false);
    document.addEventListener('touchend', viewModel.orbitMouseUpFunction, false);
    viewModel._unsubscribeFromClockTick = viewModel.props.viewer.clock.onTick.addEventListener(viewModel.orbitTickFunction);

    updateAngleAndOpacity(cursorVector, compassElement.getBoundingClientRect().width);
}

function viewerChange(viewModel) {
    if (Cesium.defined(viewModel.props.viewer)) {
        if (viewModel._unsubscribeFromPostRender) {
            viewModel._unsubscribeFromPostRender();
            viewModel._unsubscribeFromPostRender = undefined;
        }
        viewModel._unsubscribeFromPostRender = viewModel.props.viewer.scene.postRender.addEventListener(function() {
            viewModel.setState({
                heading: viewModel.props.viewer.scene.camera.heading,
                pitch: viewModel.props.viewer.scene.camera.pitch
            });
        });
    } else {
        if (viewModel._unsubscribeFromPostRender) {
            viewModel._unsubscribeFromPostRender();
            viewModel._unsubscribeFromPostRender = undefined;
        }
        viewModel.showCompass = false;
    }
}